iT邦幫忙

2024 iThome 鐵人賽

DAY 17
0
自我挑戰組

30 天 vueuse 原始碼閱讀與實作系列 第 17

[Day 17] useParallax - 官網文件 Demo 效果實作

  • 分享至 

  • xImage
  •  

為了這個簡單有趣的效果,從 Day7 一路寫到現在 Day17 也是滿妙的,太小看這個 API 了 XD

官方 Demo:https://vueuse.org/core/useParallax/#useparallax

先切版

這邊先不加入視差特效,先從切版來了解 HTML 結構以及樣式部分是怎麼設計的,說明用註解的方式寫在以下程式碼內:

<script setup>
import { computed, reactive, ref } from 'vue'
import { useParallax } from '@/compositions/useParallax'

const target = ref(null)

const parallax = reactive(useParallax(target))

// 整個視差效果的容器樣式,有多做一個 border 標示,最外層的 border 就是這個 target 的範圍
const targetStyle = {
  display: 'flex',
  flexDirection: 'column',
  justifyContent: 'center',
  border: '1px solid #cdcdcd',
  transition: '.3s ease-out all',
}

// 主要用途 -> perspective: '300px' 建立 3D 空間,影響子元素的 3D 變換效果。
const containerStyle = {
  margin: '3em auto',
  perspective: '300px',
}

// 卡片基本樣式
const cardStyle = computed(() => ({
  background: '#fff',
  height: '20rem',
  width: '15rem',
  borderRadius: '5px',
  border: '1px solid #cdcdcd',
  overflow: 'hidden',
  transition: '.3s ease-out all',
  boxShadow: '0 0 20px 0 rgba(255, 255, 255, 0.25)',
}))

// 卡片內部的小視窗,子元素圖片都會在視窗裡呈現
const cardWindowStyle = {
  overflow: 'hidden',
  fontSize: '6rem',
  position: 'absolute',
  top: 'calc(50% - 1em)',
  left: 'calc(50% - 1em)',
  height: '2em',
  width: '2em',
  margin: 'auto',
}

// 定義所有圖層的基本樣式,讓他們填滿卡片內部的小視窗
const layerBase = {
  position: 'absolute',
  height: '100%',
  width: '100%',
  transition: '.3s ease-out all',
}

const layer0 = computed(() => ({
  ...layerBase,
}))

const layer1 = computed(() => ({
  ...layerBase,
}))

const layer2 = computed(() => ({
  ...layerBase,
}))

const layer3 = computed(() => ({
  ...layerBase,
}))

const layer4 = layerBase
</script>

<template>
  <div>
    <div ref="target" :style="targetStyle">
      <div :style="containerStyle">
        <div :style="cardStyle">
          <div :style="cardWindowStyle">
            <img
              :style="layer0"
              src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer0.png"
              alt=""
            >
            <img
              :style="layer1"
              src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer1.png"
              alt=""
            >
            <img
              :style="layer2"
              src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer2.png"
              alt=""
            >
            <img
              :style="layer3"
              src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer3.png"
              alt=""
            >
            <img
              :style="layer4"
              src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer4.png"
              alt=""
            >
          </div>
        </div>
      </div>
      <div class="note opacity-1">
        Credit of images to
        <a
          href="https://codepen.io/jaromvogel"
          target="__blank"
        >Jarom Vogel</a>
      </div>
    </div>
  </div>
</template>

每一個圖層的圖片,可以在官方 Demo 中用 chrome 開發者工具看到,這邊怕版面太亂,就不貼上來。
這邊 layer4 比較有趣一點,看起來是用來做不規則圓角的遮罩,或是可以想像成相框(可以把 cardStyle 的背景色改成黑色觀察看看)。另外他在 HTML 中被排在最後一個是有用意的,如果是在第一個,就會被後面的元素蓋過,導致沒有相框效果(或是要額外 z-index 處理)。

幫卡片上特效

// ...略
const cardStyle = computed(() => ({
  // ...略
  transform: `rotateX(${parallax.roll * 20}deg) rotateY(${
    parallax.tilt * 20
  }deg)`,
}))

這邊要釐清 rotateX 跟 parallax.roll、rotateY 跟 parallax.tilt 的關係,以 rotateX 為例,
rotateX 是讓元素繞著 X 軸轉,roll 也是一樣的特性(roll 的相關資訊可參考 Day 16
所以當滑鼠從元素原點往上移動時,roll 值也會越來越大(最大 0.5),rotateX 的角度也會越來越大,這樣就可以達成滑鼠往上移,元素跟著傾斜的效果。

幫圖層上特效

// ...略

const layer0 = computed(() => ({
  ...layerBase,
  transform: `translateX(${parallax.tilt * 10}px) translateY(${
    parallax.roll * 10
  }px) scale(1.33)`,
}))

const layer1 = computed(() => ({
  ...layerBase,
  transform: `translateX(${parallax.tilt * 20}px) translateY(${
    parallax.roll * 20
  }px) scale(1.33)`,
}))

const layer2 = computed(() => ({
  ...layerBase,
  transform: `translateX(${parallax.tilt * 30}px) translateY(${
    parallax.roll * 30
  }px) scale(1.33)`,
}))

const layer3 = computed(() => ({
  ...layerBase,
  transform: `translateX(${parallax.tilt * 40}px) translateY(${
    parallax.roll * 40
  }px) scale(1.33)`,
}))

// ...略

這邊做的事其實跟前面的 cardStyle 類似,差別在用 translateX、translateY 做效果。另外看到有 x10、x20、x30、x40 不同的倍率,可以想成我們坐在火車上看窗外的風景,最遠的山看起來移動緩慢,最近的草(?)看起來移動最快,所以 x40 就是離我們最近的圖層,要讓他移動幅度大一點,效果比較逼真。

GitHub:https://github.com/RhinoLee/30days_vue/pull/16/files


為了完成這個視差效果,中間一路看了 useEventListener、useMouse、useMouseInElement、useMounted、useSupported、最後的 useParallx,這種感覺其實還不錯,有一種已經完賽的感覺(並沒有)。

明天開始就來看另一個滿常用的 useInfiniteScroll API,節奏應該會跟 useParallx 一樣,中間有用到其他的 vueuse API 就會先進去看,最後再回到 useInfiniteScroll 本身。


上一篇
[Day 16] useParallax - source code
下一篇
[Day 18] useDebounceFn
系列文
30 天 vueuse 原始碼閱讀與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言